package worker

import (
	"context"
	"gitee.com/zhengjianfeng/crontab-worker/common"
	"go.etcd.io/etcd/clientv3"
	"time"
)

var (
	G_workerJoblock *WorkerJoblock
)

// 分布式事务锁(txn事务)
type WorkerJoblock struct {
	// etcd客户端
	kv         clientv3.KV
	lease      clientv3.Lease
	jobName    string             // 任务名
	cancelFunc context.CancelFunc // 用于终止自动续租
	leaseId    clientv3.LeaseID   // 租约ID
	isLocked   bool               // 是否上锁
}

// 初始化一把锁
func InitJoblock(jobName string, kv clientv3.KV, lease clientv3.Lease) (workerJoblock *WorkerJoblock) {

	workerJoblock = &WorkerJoblock{
		kv:      kv,
		lease:   lease,
		jobName: jobName,
	}

	return
}

// 尝试上锁
func (workerJoblock *WorkerJoblock) TryLock() (err error) {

	var (
		leaseGrantResp *clientv3.LeaseGrantResponse
		cancelCtx      context.Context
		cancelFunc     context.CancelFunc
		leaseId        clientv3.LeaseID
		keepRespChan   <-chan *clientv3.LeaseKeepAliveResponse
		txn            clientv3.Txn
		lockKey        string
		txnResp        *clientv3.TxnResponse
	)
	// 1、创建租约
	if leaseGrantResp, err = workerJoblock.lease.Grant(context.TODO(), 5); err != nil {
		return
	}

	// 创建context用于取消自动续租
	cancelCtx, cancelFunc = context.WithCancel(context.TODO())

	// 2、自动续租
	leaseId = leaseGrantResp.ID
	if keepRespChan, err = workerJoblock.lease.KeepAlive(cancelCtx, leaseId); err != nil {
		goto FAIL
	}

	// 处理续租应答的协程
	go func() {
		var (
			keepResp *clientv3.LeaseKeepAliveResponse
		)
		for {
			select {
			case keepResp = <-keepRespChan: // 自动续租应答
				if keepResp == nil {
					goto END
				}
			}
			time.Sleep(1 * time.Second)
		}
	END:
	}()

	// 3、创建事务txn
	txn = workerJoblock.kv.Txn(context.TODO())

	// 锁路径
	lockKey = common.WORKER_JOB_LOCK_DIR + workerJoblock.jobName

	// 4、事务抢锁
	txn.If(clientv3.Compare(clientv3.CreateRevision(lockKey), "=", 0)).Then(clientv3.OpPut(lockKey, "", clientv3.WithLease(leaseId))).Else(clientv3.OpGet(lockKey))

	// 提交事务
	if txnResp, err = txn.Commit(); err != nil {
		goto FAIL
	}

	// 成功返回，失败释放租约
	if !txnResp.Succeeded {
		// 锁被占用
		err = common.ERR_LOCK_ALREADY_REQUIRED
		goto FAIL
	}

	// 抢锁成功
	workerJoblock.leaseId = leaseId
	workerJoblock.cancelFunc = cancelFunc
	workerJoblock.isLocked = true

	return
FAIL:
	cancelFunc() // 取消自动续租
	_, _ = workerJoblock.lease.Revoke(context.TODO(), leaseId)
	return
}

// 释放锁
func (workerJoblock *WorkerJoblock) UnLock() {
	if workerJoblock.isLocked {
		workerJoblock.cancelFunc()                                               // 取消我们程序自动续租协程
		_, _ = workerJoblock.lease.Revoke(context.TODO(), workerJoblock.leaseId) // 释放租约
	}
}
